import org.newdawn.slick.*;
import org.newdawn.slick.geom.*;
import java.util.ArrayList;

/**
 * The level controller used in singleplayer games. Will handle level loading/generation while the game is played.
 * 
 * @author Gerrit Jamerson, CodeMonkeys
 * @version 4/3/13
 */
public class SPLevelController implements ILevelController
{
    private final int intLevelSize = 500;           //The size of the level array
    private final int intTileSize = 32;             //Standard size of each tile
    private TileObject[][] tileArray;               //The array of tiles
    private ArrayList<GameObject> activeObjectList;   //List of active tiles
    private ArrayList<GameObject> collidableObjectList; //List of objects that are collidable
    private Vector2f cameraPos;

    /**
     * Constructs the level controller with default values
     */
    public SPLevelController()
    {
        collidableObjectList = new ArrayList<GameObject>();
        tileArray = new TileObject[intLevelSize][intLevelSize];
        activeObjectList = new ArrayList<GameObject>();

        System.out.println("Singleplayer Level Controller created.");
        cameraPos = new Vector2f(0, 0);
    } //End constructor

    /**
     * Instantiates the gameObject. Called from the GameObject super.
     * 
     * @pre None
     * @param gameObject The GameObject
     * @return void
     * @post gameObject has been added to the level
     */
    public void instantiate(GameObject gameObject)
    {
        if(!activeObjectList.contains(gameObject))
        {
            activeObjectList.add(gameObject);
        } //End if method
    } //End instantiate method

    /**
     * Destroys the gameObject. Called from the GameObject super.
     * 
     * @pre None
     * @param gameObject The GameObject
     * @return void
     * @post gameObject has been removed from the level
     */
    public void destroy(GameObject gameObject)
    {
        activeObjectList.remove(gameObject);
    } //End destroy method

    /**
     * Returns the game object list.
     * 
     * @pre None
     * @param None
     * @return The GameObject list
     * @post None
     */
    public ArrayList<GameObject> getGameObjectList()
    {
        return activeObjectList;
    } //End getGameObjectList method

    /**
     * Returns the tile object array. This is essentially the level in tiles.
     * 
     * @pre None
     * @param None
     * @return The TileObject array
     * @post None
     */
    public TileObject[][] getTileArray()
    {
        return tileArray;
    } //End getTileArray method

    /**
     * Sets the specific tile to something.
     * Setting it to null will remove it.
     * 
     * @pre None
     * @param x The X position
     * @param y The Y position
     * @param tileObject The tile object to set at that position.
     * @return void
     * @post The tile at position (x, y) has been set to tileObject
     */
    public void setTile(int x, int y, TileObject tileObject)
    {
        if(x >= 0 && x < intLevelSize)
        {
            if(y >= 0 && y < intLevelSize)
            {
                tileArray[x][y] = tileObject;
            } //End if y within bounds
        } //End if x within bounds
    } //End setTile method

    /**
     * Called each frame. Used to draw the layer.
     * 
     * @pre None
     * @param gameContainer The gameContainer
     * @param graphics The Graphics class
     * @return void
     * @post The layer is drawn onto the screen, if needed
     */
    public void render(GameContainer gameContainer, Graphics graphics)
    {
        graphics.translate(cameraPos.x, cameraPos.y);

        for(GameObject gameObject : activeObjectList)
        {
            Vector2f camPos = cameraPos.negate();
            Vector2f pos = gameObject.getPosition();
            float xCheck = Settings.getResolutionX() * 1.3f;
            float yCheck = Settings.getResolutionY() * 1.3f;
            if(pos.x < camPos.x + xCheck && pos.x > camPos.x - xCheck)
            {
                if(pos.y < camPos.y + yCheck && pos.y > camPos.y - yCheck)
                {
                    gameObject.render(gameContainer, graphics);
                } //End if
            } //End if
        } //End foreach loop
    } //End render method

    /**
     * Called each step. Used to update the layer.
     * 
     * @pre None
     * @param gameContainer The gameContainer
     * @param intDelta The delta time between updates
     * @return void
     * @post The layer is drawn onto the screen, if needed
     */
    public void update(GameContainer gameContainer, int intDelta)
    {
        for(GameObject gameObject : activeObjectList)
        {
            gameObject.spStep(gameContainer, intDelta);
            gameObject.update(gameContainer, intDelta);
        } //End for-each loop

        ArrayList<CollisionData> collisions = broadPhaseDetection();
        for(CollisionData collision : collisions)
        {
            collision.getFirst().onCollision(collision);
            collision.getSecond().onCollision(collision);
        } //End for-each loop
    } //End update method

    /**
     * Handles broad collision checking.
     * 
     * @pre None
     * @param None
     * @return A list of possible collisions.
     * @post None
     */
    private ArrayList<CollisionData> broadPhaseDetection()
    {
        ArrayList<CollisionData> collisions = new ArrayList<CollisionData>();
        for(int i = 0; i < collidableObjectList.size() - 1; i++)
        {
            GameObject first, second;
            first = collidableObjectList.get(i);

            if(first.getClassName() == "TileWall")
            {
                continue;
            } //End if

            for(int o = i + 1; o < collidableObjectList.size(); o++)
            {
                second = collidableObjectList.get(o);
                
                if(first.collidesWith(second))
                {
                    collisions.add(new CollisionData(first, second));
                } //End if
            } //End nested for loop
        } //End for loop
        return narrowPhaseDetection(collisions);
    } //End broadPhaseDetection method

    /**
     * Handles narrow phase collision checking.
     * 
     * @pre None
     * @param broadPhaseData A list of the possible collisions from the broad phase.
     * @return A list of collisions.
     * @post None
     */
    private ArrayList<CollisionData> narrowPhaseDetection(ArrayList<CollisionData> broadPhaseData)
    {
        ArrayList<CollisionData> collisions = new ArrayList<CollisionData>();
        for(CollisionData col : broadPhaseData)
        {
            GameObject first, second;
            first = col.getFirst();
            second = col.getSecond();

            if(first.getIgnoreObjectList().contains(second))
            {
                continue;
            } //End if

            if(second.getIgnoreObjectList().contains(first))
            {
                continue;
            } //End if

            if(first.getIgnoreClassList().contains(second.getClassName()))
            {
                continue;
            }

            if(second.getIgnoreClassList().contains(first.getClassName()))
            {
                continue;
            }

            collisions.add(col);
        } //End for

        return collisions;
    } //End narrowPhaseDetection method

    /**
     * Checks if the game object collides with a specific type of object
     */
    public boolean checkCollidesWith(GameObject gameObject, String type, Vector2f pos)
    {
        GameObject first, second;
        first = gameObject;    
        for(int o = 0; o < collidableObjectList.size(); o++)
        {
            second = collidableObjectList.get(o);
            if(second.getPosition().distance(first.getPosition()) > 500f)
            {
                continue;
            }
            
            if(second.getClassName() == type && first.collidesWith(second, pos))
            {
                return true;
            } //End if
        } //End nested for loop

        return false;
    } //End checkCollidesWith method

    /**
     * Adds the gameobject to a list of collidable objects.
     */
    public void addCollidable(GameObject gameObject)
    {
        if(!collidableObjectList.contains(gameObject))
        {
            collidableObjectList.add(gameObject);
        } //End if
    } //End addCollidable method

    /**
     * Removes the game object from the list of collidable objects.
     */
    public void removeCollidable(GameObject gameObject)
    {
        collidableObjectList.remove(gameObject);
    }

    /**
     * Sets the position of the camera.
     */
    public void setCameraPos(Vector2f pos)
    {
        cameraPos = pos;
    } //End setCameraPos method

    /**
     * Gets the position of the cammera.
     */
    public Vector2f getCameraPos()
    {
        return cameraPos;
    } //End getCameraPos method

    /**
     * Loads the level (Maybe procedural generation)
     * Implementation TBD
     */
    public void loadLevel()
    {

    } //End loadLevel method

    /**
     * Unloads the level from memory
     * Implementation TBD
     */
    public void unloadLevel()
    {

    } //End unloadLevel method
} //End class
